#include "CJosmParser.h"

//#define DEBUG

using namespace std;

////////////////////////////////////////////////////////
//////////////      Map Loading     ////////////////////
////////////////////////////////////////////////////////
bool
CJosmParser::LoadMap(const char *file, COsmMap *map)
{
    xmlDocPtr doc;
    xmlNodePtr cur;

    // Load file and check it
    doc = xmlParseFile(file);
    if(doc == NULL)
    {
        cerr << "File " << file << " canot be parsed." <<endl;
        return false;
    }
    cur = xmlDocGetRootElement(doc);
    if(cur == NULL)
    {
        cerr << "Empty document." << endl;
        xmlFreeDoc(doc);
        return false;
    }
    if(xmlStrcmp(cur->name,(const xmlChar *) "osm"))
    {
        cerr << "File " << file << " has not JOSM format." << endl;
        xmlFreeDoc(doc);
        return false;
    }

    // Parse file
    cur = cur->xmlChildrenNode;
    for(;cur != NULL;cur = cur->next)
    {
        if(cur->type != 1)
            continue;
        
        bool result = true;
        // Parse JOSM bounds
        if(!xmlStrcmp(cur->name,(const xmlChar*)"bounds"))
        {
            result = parseOSMBounds(cur,map->bounds);
            if(!result)
                cerr << "Fail to parse bounds" << endl;
#ifdef DEBUG
            else
                map->bounds->Print();
#endif
        }
        // Parse OSM nodes
        else if(!xmlStrcmp(cur->name,(const xmlChar*)"node"))
        {
            result = parseOSMNode(cur,map->nodes);
            if(!result)
                cerr << "Fail to parse node" << endl;
#ifdef DEBUG
            else
                map->nodes[map->nodes.size()-1].Print();
#endif
        }
        // Parse OSM Ways
        else if(!xmlStrcmp(cur->name,(const xmlChar*)"way"))
        {
            result = parseOSMWay(cur,map,map->ways);
            if(!result)
                cerr << "Fail to parse way" << endl;
#ifdef DEBUG
            else
                map->ways[map->ways.size()-1].Print();
#endif
        }
        // Parse OSM relations
        else if(!xmlStrcmp(cur->name,(const xmlChar*)"relation"))
        {
            result = parseOSMRelation(cur,map->relations);
            if(!result)
                cerr << "Fail to parse realtion" << endl;
#ifdef DEBUG
            else
                map->relations[map->relations.size()-1].Print();
#endif
        }
        else
            cout << "Unknown node type: " << cur->name << endl;
        if(!result)
            cerr << "Parsing failed!" << endl;
    }
    // Free file
    xmlFreeDoc(doc);
    // Find neighbours for every node
    findNeighbours(map->nodes,map->ways);
    return true;
}

bool
CJosmParser::parseOSMBounds(xmlNodePtr cur,COsmBounds *bounds)
{
    xmlChar *att;
    att = xmlGetProp(cur,(const xmlChar*)"minlat");
    if(att != NULL)
        sscanf((char*)att,"%lf",&bounds->minlat);
    att = xmlGetProp(cur,(const xmlChar*)"minlon");
    if(att != NULL)
        sscanf((char*)att,"%lf",&bounds->minlon);
    att = xmlGetProp(cur,(const xmlChar*)"maxlat");
    if(att != NULL)
        sscanf((char*)att,"%lf",&bounds->maxlat);
    att = xmlGetProp(cur,(const xmlChar*)"maxlon");
    if(att != NULL)
        sscanf((char*)att,"%lf",&bounds->maxlon);
    att = xmlGetProp(cur,(const xmlChar*)"origin");
    if(att != NULL)
        bounds->origin = att;
    return true;
}

bool
CJosmParser::parseOSMNode(xmlNodePtr cur,vector<COsmNode > &nodes)
{
    COsmNodePtr node = new COsmNode();
    xmlChar *att;
    att = xmlGetProp(cur,(const xmlChar*)"id");
    if(att != NULL)
        sscanf((char*)att,"%ld",&node->id);
    att = xmlGetProp(cur,(const xmlChar*)"visible");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"true"))
            node->visible = true;
        else
            node->visible = false;
    }
    att = xmlGetProp(cur,(const xmlChar*)"version");
    if(att != NULL)
        sscanf((char*)att,"%d",&node->version);
    att = xmlGetProp(cur,(const xmlChar*)"timestamp");
    if(att != NULL)
        node->timestamp = att;
    att = xmlGetProp(cur,(const xmlChar*)"changeset");
    if(att != NULL)
        sscanf((char*)att,"%ld",&node->changeset);
    att = xmlGetProp(cur,(const xmlChar*)"user");
    if(att != NULL)
        node->user = att;
    att = xmlGetProp(cur,(const xmlChar*)"uid");
    if(att != NULL)
        sscanf((char*)att,"%ld",&node->uid);
    att = xmlGetProp(cur,(const xmlChar*)"action");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"delete"))
            node->action = JOSM_ACTION_DEL;
        else
            node->action = JOSM_ACTION_MOD;
    }
    att = xmlGetProp(cur,(const xmlChar*)"lon");
    if(att != NULL)
        sscanf((char*)att,"%lf",&node->lon);
    att = xmlGetProp(cur,(const xmlChar*)"lat");
    if(att != NULL)
        sscanf((char*)att,"%lf",&node->lat);
    parseOSMTags(cur,node->tags);
    nodes.push_back(*node);
    return true;
}

bool
CJosmParser::parseOSMWay(xmlNodePtr cur,COsmMap *map, vector<COsmWay > &ways)
{
    COsmWayPtr way = new COsmWay();
    xmlChar *att;
    att = xmlGetProp(cur,(const xmlChar*)"id");
    if(att != NULL)
        sscanf((char*)att,"%ld",&way->id);
    att = xmlGetProp(cur,(const xmlChar*)"visible");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"true"))
            way->visible = true;
        else
            way->visible = false;
    }
    att = xmlGetProp(cur,(const xmlChar*)"version");
    if(att != NULL)
        sscanf((char*)att,"%d",&way->version);
    att = xmlGetProp(cur,(const xmlChar*)"timestamp");
    if(att != NULL)
        way->timestamp = att;
    att = xmlGetProp(cur,(const xmlChar*)"changeset");
    if(att != NULL)
        sscanf((char*)att,"%ld",&way->changeset);
    att = xmlGetProp(cur,(const xmlChar*)"user");
    if(att != NULL)
        way->user = att;
    att = xmlGetProp(cur,(const xmlChar*)"uid");
    if(att != NULL)
        sscanf((char*)att,"%ld",&way->uid);
    att = xmlGetProp(cur,(const xmlChar*)"action");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"delete"))
            way->action = JOSM_ACTION_DEL;
        else
            way->action = JOSM_ACTION_MOD;
    }
    parseOSMNds(cur,way->nodes,map);
    parseOSMTags(cur,way->tags);
    ways.push_back(*way);
    return true;
}

bool
CJosmParser::parseOSMRelation(xmlNodePtr cur,vector<COsmRelation > &relations)
{
    COsmRelationPtr relation = new COsmRelation();
    xmlChar *att;
    att = xmlGetProp(cur,(const xmlChar*)"id");
    if(att != NULL)
        sscanf((char*)att,"%ld",&relation->id);
    att = xmlGetProp(cur,(const xmlChar*)"visible");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"true"))
            relation->visible = true;
        else
            relation->visible = false;
    }
    att = xmlGetProp(cur,(const xmlChar*)"version");
    if(att != NULL)
        sscanf((char*)att,"%d",&relation->version);
    att = xmlGetProp(cur,(const xmlChar*)"timestamp");
    if(att != NULL)
        relation->timestamp = att;
    att = xmlGetProp(cur,(const xmlChar*)"changeset");
    if(att != NULL)
        sscanf((char*)att,"%ld",&relation->changeset);
    att = xmlGetProp(cur,(const xmlChar*)"user");
    if(att != NULL)
        relation->user = att;
    att = xmlGetProp(cur,(const xmlChar*)"uid");
    if(att != NULL)
        sscanf((char*)att,"%ld",&relation->uid);
    att = xmlGetProp(cur,(const xmlChar*)"action");
    if(att != NULL)
    {
        if(!xmlStrcmp(att,(const xmlChar*)"delete"))
            relation->action = JOSM_ACTION_DEL;
        else
            relation->action = JOSM_ACTION_MOD;
    }
    parseOSMMembers(cur,relation->members);
    parseOSMTags(cur,relation->tags);
    relations.push_back(*relation);
    return true;
}

bool 
CJosmParser::parseOSMTags(xmlNodePtr cur,vector<tag_type > &tags)
{
    xmlChar* att;
    tag_type tag;
    tag.first = (const xmlChar*)"";
    tag.second = (const xmlChar*)"";
    for(cur = cur->xmlChildrenNode;cur != NULL;cur = cur->next)
    {
        if(!xmlStrcmp(cur->name,(const xmlChar*)"tag"))
        {
            att = xmlGetProp(cur,(const xmlChar*)"k");
            if(att != NULL)
                tag.first = att;
            att = xmlGetProp(cur,(const xmlChar*)"v");
            if(att != NULL)
                tag.second = att;
            tags.push_back(tag);
        }
    }
    return true;
}

bool 
CJosmParser::parseOSMNds(xmlNodePtr cur,vector<long int > &nds,COsmMap *map)
{
    xmlChar* att;
    long int id;
    long int node;
    for(cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next)
    {
        if(!xmlStrcmp(cur->name,(const xmlChar*)"nd"))
        {
            att = xmlGetProp(cur,(const xmlChar*)"ref");
            if(att != NULL)
            {
                sscanf((char*)att,"%ld",&id);
                node = map->getOsmNode(id);
                nds.push_back(node);
            }
        }
    }

    return true;
}

bool 
CJosmParser::parseOSMMembers(xmlNodePtr cur,vector<member_type > &members)
{
    member_type m;
    xmlChar* att;
    for(cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next)
    {
        if(!xmlStrcmp(cur->name,(const xmlChar*)"member"))
        {
            att = xmlGetProp(cur,(const xmlChar*)"type");
            if(att != NULL)
                m.type = att;
            att = xmlGetProp(cur,(const xmlChar*)"role");
            if(att != NULL)
                m.role = att;
            att = xmlGetProp(cur,(const xmlChar*)"ref");
            if(att != NULL)
                m.ref = att;
            members.push_back(m);
        }
    }
    return true;
}

void
CJosmParser::findNeighbours(vector<COsmNode > &nodes, vector<COsmWay > &ways)
{
    unsigned int i,j;
    for(i = 0; i < ways.size(); i++)
    {
        for(j = 0; j < ways[i].nodes.size(); j++)
        {
            if(j > 0)
                nodes[ways[i].nodes[j]].neighbours.push_back(ways[i].nodes[j-1]);
            if(j < ways[i].nodes.size()-1)
                nodes[ways[i].nodes[j]].neighbours.push_back(ways[i].nodes[j+1]);
        }
    }
}

////////////////////////////////////////////////////////
//////////////      Map Saving      ////////////////////
////////////////////////////////////////////////////////

bool
CJosmParser::SaveMap(const char* filename, COsmMap *map)
{
    unsigned long int n,w,r,t;
//    stringstream ss;
    xmlDocPtr doc = NULL;
    xmlNodePtr root_node = NULL;
    xmlNodePtr node = NULL;
    xmlNodePtr sNode = NULL;
    xmlChar c[100];

    LIBXML_TEST_VERSION;

    doc = xmlNewDoc((const xmlChar*) "1.0");
    root_node = xmlNewNode(NULL,(const xmlChar*) "osm");
    xmlNewProp(root_node,(const xmlChar*) "version",(const xmlChar*) "0.6");
    xmlNewProp(root_node,(const xmlChar*) "generator", (const xmlChar*) "JOSM");
    xmlDocSetRootElement(doc,root_node);

    // bounds
    node = xmlNewChild(root_node,NULL,(const xmlChar*) "bounds",NULL);
    toXmlChar(map->bounds->minlat,c);
    xmlNewProp(node,(const xmlChar*) "minlat",c);
    toXmlChar(map->bounds->minlon,c);
    xmlNewProp(node,(const xmlChar*) "minlon",c);
    toXmlChar(map->bounds->maxlat,c);
    xmlNewProp(node,(const xmlChar*) "maxlat",c);
    toXmlChar(map->bounds->maxlon,c);
    xmlNewProp(node,(const xmlChar*) "maxlon",c);
    if(xmlStrcmp(map->bounds->origin,(const xmlChar*)""))
        xmlNewProp(node,(const xmlChar*) "origin",map->bounds->origin);

    // Save nodes
    for(n = 0; n < map->nodes.size(); n++)
    {
        node = xmlNewChild(root_node,NULL,(const xmlChar*) "node",NULL);
        toXmlChar(map->nodes[n].id,c);
        xmlNewProp(node,(const xmlChar*) "id",c);
        xmlNewProp(node,(const xmlChar*) "timestamp",map->nodes[n].timestamp);
        toXmlChar(map->nodes[n].uid,c);
        xmlNewProp(node,(const xmlChar*) "uid",c);
        xmlNewProp(node,(const xmlChar*) "user",map->nodes[n].user);
        if(map->nodes[n].visible)
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*)"true");
        else
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*)"false");
        toXmlChar(map->nodes[n].version,c);
        xmlNewProp(node,(const xmlChar*) "version",c);
        toXmlChar(map->nodes[n].changeset,c);
        xmlNewProp(node,(const xmlChar*) "changeset",c);
        toXmlChar(map->nodes[n].lat,c);
        xmlNewProp(node,(const xmlChar*) "lat",c);
        toXmlChar(map->nodes[n].lon,c);
        xmlNewProp(node,(const xmlChar*) "lon",c);
        if(xmlStrcmp(map->nodes[n].action,JOSM_ACTION_NON))
            xmlNewProp(node,(const xmlChar*) "action",map->nodes[n].action);
        // Save node tags
        for(t = 0; t < map->nodes[n].tags.size(); t++)
        {
            sNode = xmlNewChild(node,NULL,(const xmlChar*) "tag",NULL);
            xmlNewProp(sNode,(const xmlChar*) "k",map->nodes[n].tags[t].first);
            xmlNewProp(sNode,(const xmlChar*) "v",map->nodes[n].tags[t].second);
        }
    }
    // Save wayS
    for(w = 0; w < map->ways.size(); w++)
    {
        node = xmlNewChild(root_node,NULL,(const xmlChar*) "way",NULL);
        toXmlChar(map->ways[w].id,c);
        xmlNewProp(node,(const xmlChar*) "id",c);
        xmlNewProp(node,(const xmlChar*) "timestamp",map->ways[w].timestamp);
        toXmlChar(map->ways[w].uid,c);
        xmlNewProp(node,(const xmlChar*) "uid",c);
        xmlNewProp(node,(const xmlChar*) "user",map->ways[w].user);
        if(map->ways[w].visible)
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*)"true");
        else
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*)"false");
        toXmlChar(map->ways[w].version,c);
        xmlNewProp(node,(const xmlChar*) "version",c);
        toXmlChar(map->ways[w].changeset,c);
        xmlNewProp(node,(const xmlChar*) "changeset",c);
        if(xmlStrcmp(map->ways[w].action,JOSM_ACTION_NON))
            xmlNewProp(node,(const xmlChar*) "action",map->ways[w].action);
        // Save way nodes ref
        for(n = 0; n < map->ways[w].nodes.size(); n++)
        {
            sNode = xmlNewChild(node,NULL,(const xmlChar*)"nd",NULL);
            toXmlChar(map->nodes[map->ways[w].nodes[n]].id,c);
            xmlNewProp(sNode,(const xmlChar*)"ref",c);
        }
        // Save way tags
        for(t = 0; t < map->ways[w].tags.size(); t++)
        {
            sNode = xmlNewChild(node,NULL,(const xmlChar*) "tag",NULL);
            xmlNewProp(sNode,(const xmlChar*) "k",map->ways[w].tags[t].first);
            xmlNewProp(sNode,(const xmlChar*) "v",map->ways[w].tags[t].second);
        }
    }
    // Save relations
    for(r = 0; r < map->relations.size(); r++)
    {
        node = xmlNewChild(root_node,NULL,(const xmlChar*) "relation",NULL);
        toXmlChar(map->relations[r].id,c);
        xmlNewProp(node,(const xmlChar*) "id",c);
        xmlNewProp(node,(const xmlChar*) "timestamp",map->relations[r].timestamp);
        toXmlChar(map->relations[r].uid,c);
        xmlNewProp(node,(const xmlChar*) "uid",c);
        xmlNewProp(node,(const xmlChar*) "user",map->relations[r].user);
        if(map->relations[r].visible)
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*) "true");
        else
            xmlNewProp(node,(const xmlChar*) "visible",(const xmlChar*) "false");
        toXmlChar(map->relations[r].version,c);
        xmlNewProp(node,(const xmlChar*) "version",c);
        toXmlChar(map->relations[r].changeset,c);
        xmlNewProp(node,(const xmlChar*) "changeset",c);
        if(xmlStrcmp(map->relations[r].action,JOSM_ACTION_NON))
            xmlNewProp(node,(const xmlChar*) "action",map->relations[r].action);
        // Save realation references
        for(n = 0; n < map->relations[r].members.size(); n++)
        {
            sNode = xmlNewChild(node,NULL,(const xmlChar*)"member",NULL);
            xmlNewProp(sNode,(const xmlChar*)"ref",map->relations[r].members[n].ref);
            xmlNewProp(sNode,(const xmlChar*)"type",map->relations[r].members[n].type);
            xmlNewProp(sNode,(const xmlChar*)"role",map->relations[r].members[n].role);
        }
        // Save way tags
        for(t = 0; t < map->relations[r].tags.size(); t++)
        {
            sNode = xmlNewChild(node,NULL,(const xmlChar*) "tag",NULL);
            xmlNewProp(sNode,(const xmlChar*) "k",map->relations[r].tags[t].first);
            xmlNewProp(sNode,(const xmlChar*) "v",map->relations[r].tags[t].second);
        }
    }
  
    xmlSaveFormatFileEnc(filename,doc,"UTF-8",1);
    xmlFreeDoc(doc);
    xmlCleanupParser();
    xmlMemoryDump();
    return true;
}
